package com.ubb.smartsearch.classifier.rocchio;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.ejb.Stateless;

import com.ubb.smartsearch.classifier.ClassifierInterface;
import com.ubb.smartsearch.classifier.rocchio.entity.Category;
import com.ubb.smartsearch.classifier.rocchio.entity.Tuple;
import com.ubb.smartsearch.classifier.tool.Utils;

/**
 * 
 */

/**
 * @author Aron
 * 
 */
@Stateless
public class RocchioClassifierBean implements ClassifierInterface {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private int wordIndexGen = 0;
	private Map<String, Integer> wordDataBase = new HashMap<String, Integer>();
	private List<Category> categories;
	private double alfa;
	private double beta;

	public String classifyDocument(String url) {

		System.out.println("Rocchio Classifier");
		String text = Utils.extractTextFromURL(url);
		if(text == null) {
			return null;
		}
		List<String> tokens = Utils.tokenize(text);
		List<Tuple> documentRepresentation = countWordsInDocument(tokens,
				new ArrayList<Tuple>(), false);

		double norm = 0.0;
		for (Tuple actual : documentRepresentation) {
			norm += actual.getWordNumber() * actual.getWordNumber();
		}
		norm = Math.sqrt(norm);

		// double test = 0.0;
		for (int i = 0; i < documentRepresentation.size(); ++i) {
			Tuple temp = documentRepresentation.get(i);
			double testTemp = temp.getWordNumber() / norm;
			temp.setWordNumber(testTemp);
			documentRepresentation.set(i, temp);
			// test += testTemp * testTemp;
		}
		// System.out.println(Math.sqrt(test));

		String minCategory = null;
		String maxCategory = null;
		double min = Double.MAX_VALUE;
		double max = -Double.MAX_VALUE;
		for (Category cat : categories) {
			Map<Integer, Double> protoType = ((Category) cat).getProtoType();
			double sum = 0.0;
			for (Tuple word : documentRepresentation) {
				Double key = protoType.get(word.getWordIndex());
				if (key != null) {
					sum += word.getWordNumber() * key;
				}
			}
			// System.out.println(cat.getCategoryName() + " is: " + sum);
			if (sum < min) {
				min = sum;
				minCategory = cat.getCategoryName();
			}
			if (sum > max) {
				max = sum;
				maxCategory = cat.getCategoryName();
			}
		}

		System.out.println(maxCategory + " -- max -- " + max);
		System.out.println(minCategory + " -- min -- " + min);

		return maxCategory;
	}

	private void addWordToTrainFileRepresentation(
			List<Tuple> trainFileRepresentation, int index, double number) {
		Tuple newWord = new Tuple();
		newWord.setWordIndex(index);
		newWord.setWordNumber(number);
		trainFileRepresentation.add(newWord);
	}

	public void trainCategories(String trainCorpus) throws IOException {

		File trainFolder = Utils.getTrainFolderOfCorpus(trainCorpus);
		categories = new ArrayList<Category>();
		for (File categoryFolder : trainFolder.listFiles()) {
			Category category = new Category();
			category.setCategoryName(categoryFolder.getName());

			File[] trainFiles = categoryFolder.listFiles();
			category.setTrainFilesRepresentation(new ArrayList<List<Tuple>>(
					trainFiles.length));

			for (File trainFile : trainFiles) {
				List<String> tokens = Utils.tokenize(Utils.readFile(trainFile
						.getPath()));
				List<Tuple> trainFileRepresentation = new ArrayList<Tuple>(
						tokens.size());
				category.addTrainFileRepresentation(countWordsInDocument(
						tokens, trainFileRepresentation, true));
			}

			categories.add(calculateWeightsInCategory(category));
		}
		buildCategoryProtoTypes();
	}

	private List<Tuple> countWordsInDocument(List<String> tokens,
			List<Tuple> trainFileRepresentation, boolean train) {
		for (String word : tokens) {
			Integer wordIndex = wordDataBase.get(word);
			if (wordIndex != null) {
				boolean found = false;
				for (int i = 0; i < trainFileRepresentation.size(); ++i) {
					Tuple actual = trainFileRepresentation.get(i);
					if (wordIndex == actual.getWordIndex()) {
						actual.incWordNumber();
						trainFileRepresentation.set(i, actual);
						found = true;
						break;
					}
				}
				if (!found) {
					addWordToTrainFileRepresentation(trainFileRepresentation,
							wordIndex, 1);
				}
			} else {
				if (train) {
					wordDataBase.put(word, wordIndexGen);
					addWordToTrainFileRepresentation(trainFileRepresentation,
							wordIndexGen, 1);
					++wordIndexGen;
				}
			}
		}
		return trainFileRepresentation;
	}

	private void buildCategoryProtoTypes() throws IOException {

		/*
		 * Itt szamolom ki a szumma(wik) -t minden kategoriara (tulajdonkepp a PosJ-nek megfelelo kategoriakra)
		 */
		for (int catI = 0; catI < categories.size(); ++catI) {
			Category category = (Category) categories.get(catI);
			Map<Integer, Double> sum = new HashMap<Integer, Double>();
			for (List<Tuple> document : category.getTrainFilesRepresentation()) {
				for (Tuple word : document) {
					Double sumMember = sum.get(word.getWordIndex());
					if (sumMember != null) {
						sumMember += word.getWordNumber();
						sum.put(word.getWordIndex(), sumMember);
					} else {
						sum.put(word.getWordIndex(), word.getWordNumber());
					}
				}
			}
			category.setProtoType(sum);
			categories.set(catI, category);
		}

		/*
		 * bejarom a kategoriakat
		 */
		for (int posI = 0; posI < categories.size(); ++posI) {

			double negLength = 0.0;
			Map<Integer, Double> negSum = new HashMap<Integer, Double>();
			Category pos = (Category) categories.get(posI);
			
			/*
			 * Itt szamolom ki a NegJ-nek megfelelo szummat.
			 * Tulajdonkepp mindig kihagyom az aktualis kategoriat, es a tobbit osszeadom.
			 */
			for (Category actualCat : categories) {
				if (pos.getCategoryName().equals(actualCat.getCategoryName())) {
					continue;
				}
				Category actualRocchioCat = (Category) actualCat;
				negLength += actualRocchioCat.getTrainFilesRepresentation()
						.size();
				for (Entry<Integer, Double> word : actualRocchioCat
						.getProtoType().entrySet()) {
					Double sumMember = negSum.get(word.getKey());
					if (sumMember != null) {
						sumMember += word.getValue();
						negSum.put(word.getKey(), sumMember);
					} else {
						negSum.put(word.getKey(), word.getValue());
					}
				}
			}

			Map<Integer, Double> posSum = pos.getProtoType();
			double posConst = alfa
					/ (double) pos.getTrainFilesRepresentation().size();
			double negConst = beta / negLength;

			/*
			 * Bejarom a PosJ-be tartozo szavakat, es megnezem, hogy van-e a NegJ-be is
			 * Ha igen, akkor kiszamolom az (alfa*szumma - beta*szumma) kepletet
			 * es kitorlom a NegJ-bol, hogy csokkentsem a meretet
			 */
			Map<Integer, Double> tempProto = new HashMap<Integer, Double>();
			for (Entry<Integer, Double> posW : posSum.entrySet()) {
				Double negW = negSum.get(posW.getKey());
				if (negW != null) {
					double temp = (posConst * posW.getValue())
							- (negConst * negW);
					tempProto.put(posW.getKey(), temp);
					negSum.remove(posW.getKey());
				} else {
					tempProto.put(posW.getKey(), posConst * posW.getValue());
				}
			}

			/*
			 * Bejarom a NegJ-be tartozo szavakat, es kiszamolom az
			 * (alfa*szumma - beta*szumma) kepletet, ahol ugy-e az elso reszet 0-nak tekintem
			 * mivel ezekbol a szavakbol nincs a PosJ-be
			 */
			for (Entry<Integer, Double> negW : negSum.entrySet()) {
				tempProto
						.put(negW.getKey(), 0.0 - (negConst * negW.getValue()));
			}

			/*
			 * Kiszamolom a normajat a kapott prototipusnak
			 */
			double norm = 0.0;
			for (Entry<Integer, Double> proto : tempProto.entrySet()) {
				norm += proto.getValue() * proto.getValue();
			}

			norm = Math.sqrt(norm);

			/*
			 * Vegig osztom a prototipust a normaval
			 */
			// double test = 0.0;
			Map<Integer, Double> result = new HashMap<Integer, Double>();
			for (Entry<Integer, Double> proto : tempProto.entrySet()) {
				double temp = proto.getValue() / norm;
				result.put(proto.getKey(), temp);
				// test += temp * temp;
			}

			// test = Math.sqrt(test);
			// System.out.println("eredmeny: " + test);
			pos.setProtoType(result);
			categories.set(posI, pos);
		}

	}

	private Category calculateWeightsInCategory(Category category) {

		Map<Integer, Double> idf = calculateIdf(category);

		List<List<Tuple>> trainFilesRepresentation = category
				.getTrainFilesRepresentation();

		// tfi - trainFilesRepresentation index
		for (int tfi = 0; tfi < trainFilesRepresentation.size(); ++tfi) {
			List<Tuple> actualTrainFile = trainFilesRepresentation.get(tfi);
			int di = actualTrainFile.size();
			double norm = 0.0;
			for (int i = 0; i < di; ++i) {
				Tuple actualWord = actualTrainFile.get(i);
				double tfidf = (actualWord.getWordNumber() / (double) di)
						* idf.get(actualWord.getWordIndex());
				norm += (tfidf * tfidf);
				actualWord.setWordNumber(tfidf);
				actualTrainFile.set(i, actualWord);
			}

			norm = Math.sqrt(norm);
			for (int i = 0; i < di; ++i) {
				Tuple actualWord = actualTrainFile.get(i);
				double normalizedValue = actualWord.getWordNumber() / norm;
				if (Double.isNaN(normalizedValue)) {
					actualWord.setWordNumber(0);
				} else {
					actualWord.setWordNumber(normalizedValue);
				}
				actualTrainFile.set(i, actualWord);
			}

			trainFilesRepresentation.set(tfi, actualTrainFile);
		}

		category.setTrainFilesRepresentation(trainFilesRepresentation);

		return category;
	}

	private Map<Integer, Double> calculateIdf(Category category) {

		Map<Integer, Double> idf = new HashMap<Integer, Double>();
		for (List<Tuple> actualTrainFile : category
				.getTrainFilesRepresentation()) {
			for (Tuple actualWord : actualTrainFile) {
				Double actual = idf.get(actualWord.getWordIndex());
				if (actual != null) {
					++actual;
				} else {
					actual = 1.0;
				}
				idf.put(actualWord.getWordIndex(), actual);
			}
		}

		int N = category.getTrainFilesRepresentation().size();
		double log2 = Math.log(2);
		Map<Integer, Double> result = new HashMap<Integer, Double>();
		for (Entry<Integer, Double> actual : idf.entrySet()) {
			Double tempIdf = Math.log((double) N / actual.getValue()) / log2;
			result.put(actual.getKey(), tempIdf);
		}

		return result;
	}

	public void setRocchioParameters(double alfa, double beta) {
		this.alfa = alfa;
		this.beta = beta;
	}
	
}
